fix(proxy): detect stalled upstream websockets - #1579
Conversation
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
# Conflicts: # app/core/clients/proxy_websocket.py # app/modules/proxy/_service/http_bridge/upstream_events.py # app/modules/proxy/_service/websocket/mixin.py # tests/unit/test_proxy_http_bridge.py
|
Reviewed watchdog lifecycle and settlement ownership. Transport side is sound: no custom watcher task, both watchdogs are library-managed, and the direct-WS send/receive settlement claim in One settlement gap in the bridge reader blocks merge: P1 — P3 — classification is pinned to library-internal strings ( P3 — residual false-positive note, no change requested: both libraries stop processing pongs while transport reading is paused under recv backpressure, so a relay stalled >~ping_interval+timeout on downstream writes can kill a healthy upstream. Bounded by the same 120s downstream idle budget and the failure is account-neutral + retryable; the ops.md monitoring note covers it. Heads-up: this rewrites the same settlement/admission-waiter region as #1594 and the reservation paths #1536 touches — whichever lands second will need a careful rebase. #1558 (finalizer settlement ordering) is merging ahead in this same territory; expect a rebase over its websocket/mixin.py finalizer changes. |
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Addressed in For the P1 settlement gap:
For the P3 library-shape coverage:
The backpressure false-positive note remains unchanged as requested; the existing account-neutral/retryable behavior and ops monitoring note still bound and document it. Local verification after the #1558 merge:
GitHub CI for |
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
@Soju06 please review |
|
Full review plus two adversarial verification passes today — verdict: merge-ready in substance. The half-open detection gap is real ( Two asks before merge:
Two P3s are fine as follow-ups, noting them for the record: (a) the aiohttp heartbeat classification is pinned to the library-internal |
Resolve the WebSocket lifecycle conflicts against current main and make the HTTP bridge sender's claimed liveness settlement survive caller cancellation.
|
Addressed in merge commit Current-main integration
P2 — cancellation after the HTTP bridge sender claim
The existing simultaneous send/receive liveness-race regression now runs in two variants:
P3 record
Local verification on
Repository-wide |
|
@Soju06 please review |
|
@codex review |
|
Codex Review: Didn't find any major issues. More of your lovely PRs please. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Summary
Detect silent upstream Responses WebSockets after a network/VPN transition and terminate affected conversations promptly instead of leaving them frozen until the container restarts.
This is a focused follow-up to #1234. That PR made observed host-network failures recoverable and account-neutral; this closes the remaining gap where an already-open WebSocket can become half-open without producing a read or write error.
Type of change
fix:— bug fix (no behavior change beyond the bug)Linked issue: None — reported directly; no matching issue was found.
OpenSpec
Change directory:
openspec/changes/archive/2026-08-04-recover-responses-websocket-liveness/Changes
heartbeatfor routed sockets and websocketsping_timeoutfor direct sockets. No new setting or background watcher is introduced.upstream_websocket_liveness_timeout, including aiohttp's stored heartbeat exception and websockets' acknowledged send-first 1011 close shape.response.create, settle every pending reservation/request exactly once, and retire the dead upstream generation.keepalive ping timeoutis account-neutral. A peer-initiated close with the same code/reason is deliberately treated as an ordinary, account-penalizing upstream failure.Simplicity
proxy_downstream_websocket_idle_timeout_secondsvalue is reused..env.example/ dashboard nav remain unchanged and within budget.Test plan
Passed locally on the current-main merge (
fdcc59b5):The cancellation regression runs the simultaneous bridge send/receive liveness race in both normal and
cancelled-after-claimvariants. In the cancellation variant, the submitter is cancelled while whole-deque settlement is deliberately paused; the test verifies both siblings are finalized exactly once, reservations/queue ownership drain, the reader does not replay or double-settle, and cancellation is preserved only after settlement completes.Three serial local Codex review rounds were completed earlier in the PR. Five P1 findings across those rounds were fixed and covered by regressions: stored aiohttp heartbeat exceptions, acknowledged direct close handshakes, bridge double settlement, bridge sibling settlement, and direct send/receive settlement ownership. Per review-round guidance, no additional local Codex review round was started for this maintainer follow-up.
Screenshots / output
No dashboard-visible change. On upstream watchdog expiry, unsequenced requests receive a
response.failedevent with codeupstream_websocket_liveness_timeout; sequenced streams close with WebSocket code 1011 after settlement.Checklist